package cc.rengu.oltp.utility.util;

import cc.rengu.oltp.utility.annotation.FIELDPATH;
import net.sf.jsqlparser.expression.DateTimeLiteralExpression;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

/**
 * XML树与实体转换工具类
 * Created by 王成 on 2017/11/24.
 */
public class ConvertUtil {

    private static final String NULL_STR = "null";

    /**
     * 将实体类SET到XML树
     *
     * @param object 实体类
     * @throws Exception 异常
     */
    public static void convertInput(Object object) throws Exception {
        convertInputCore("", object);
    }

    /**
     * 将实体类SET到XML树的核心实现
     *
     * @param xmlPathPrefix XML树字段前缀
     * @param object        实体类
     * @throws Exception 异常
     */
    private static void convertInputCore(String xmlPathPrefix, Object object) throws Exception {
        XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
        if (null == object) {
            return;
        }
        List<Field> fieldList = new ArrayList<>();
        Class<?> clasz = object.getClass();
        while (null != clasz) {
            fieldList.addAll(Arrays.asList(clasz.getDeclaredFields()));
            clasz = clasz.getSuperclass();
        }
        for (Field field : fieldList) {
            field.setAccessible(true);
            String fieldNamePath;
            String fieldTypeName = field.getType().getSimpleName();
            FIELDPATH fieldpath = field.getAnnotation(FIELDPATH.class);
            if (null != fieldpath) {
                //add by lizhangtao 如果有注解且为空字符串""则表示不希望从对象设置到树上
                if (StringUtil.isEmptyOrNull(fieldpath.value()) || NULL_STR.equalsIgnoreCase(fieldpath.value())) {
                    continue;
                }
                fieldNamePath = xmlPathPrefix.isEmpty() ? fieldpath.value() : (xmlPathPrefix + "/" + fieldpath.value());
            } else {
                fieldNamePath = xmlPathPrefix.isEmpty() ? field.getName() : (xmlPathPrefix + "/" + field.getName());
            }
            switch (fieldTypeName) {
                case "int":
                case "Integer":
                case "long":
                case "Long":
                case "short":
                case "Short":
                case "double":
                case "Double":
                case "BigDecimal":
                case "float":
                case "Float":
                case "char":
                case "Character":
                    if (null != field.get(object)) {
                        String fieldNameBaseValue = String.valueOf(field.get(object));
                        xmlTreeUtil.setXmlTreeStringValue(fieldNamePath, fieldNameBaseValue);
                    }
                    break;
                case "String":
                case "Object":
                    if (null != field.get(object)) {
                        String fieldNameStringValue = field.get(object).toString();
                        xmlTreeUtil.setXmlTreeStringValue(fieldNamePath, fieldNameStringValue);
                    }
                    break;
                case "boolean":
                case "Boolean":
                    if (null != field.get(object)) {
                        Boolean booleanValue = (Boolean) field.get(object);
                        String fieldNameBooleanValue = booleanValue ? "true" : "false";
                        xmlTreeUtil.setXmlTreeStringValue(fieldNamePath, fieldNameBooleanValue);
                    }
                    break;
                case "byte":
                case "byte[]":
                    if (null != field.get(object)) {
                        String byteStringValue = new String((byte[]) field.get(object));
                        xmlTreeUtil.setXmlTreeStringValue(fieldNamePath, byteStringValue);
                    }
                    break;
                case "Date":
                    Date dateValue = (Date) field.get(object);
                    if (null != dateValue) {
                        String fieldNameDateValue = DateUtil.formatDate(dateValue);
                        xmlTreeUtil.setXmlTreeStringValue(fieldNamePath, fieldNameDateValue);
                    }
                    break;
                case "List":
                    if (null != field.get(object)) {
                        String xmlListPathPrefix;
                        List listObject = (List) field.get(object);
                        if (null != fieldpath) {
                            xmlListPathPrefix = xmlPathPrefix.isEmpty() ? fieldpath.value() : (xmlPathPrefix + "/" + fieldpath.value());
                        } else {
                            xmlListPathPrefix = xmlPathPrefix.isEmpty() ? field.getName() : (xmlPathPrefix + "/" + field.getName());
                        }
                        Type type = field.getGenericType();
                        if (null != type) {
                            if (type instanceof ParameterizedType) {
                                ParameterizedType parameterizedType = (ParameterizedType) type;
                                if (isBaseDataType(parameterizedType.getActualTypeArguments()[0])) {
                                    for (int i = 0, k = 1; i < listObject.size(); i++, k++) {
                                        xmlTreeUtil.setXmlTreeStringValue((xmlListPathPrefix + "[" + k + "]"), (String) listObject.get(i));
                                    }
                                } else {
                                    for (int i = 0, k = 1; i < listObject.size(); i++, k++) {
                                        convertInputCore((xmlListPathPrefix + "[" + k + "]"), listObject.get(i));
                                    }
                                }
                            }
                        }
                    }
                    break;
                default:
                    String xmlObjectPathPrefix;
                    if (null != fieldpath) {
                        xmlObjectPathPrefix = xmlPathPrefix.isEmpty() ? fieldpath.value() : (xmlPathPrefix + "/" + fieldpath.value());
                    } else {
                        xmlObjectPathPrefix = xmlPathPrefix.isEmpty() ? field.getName() : (xmlPathPrefix + "/" + field.getName());
                    }
                    convertInputCore(xmlObjectPathPrefix, field.get(object));
                    break;
            }
        }
    }

    /**
     * 将XML树转换成对象实例
     *
     * @param object 被实例化的对象类
     * @return 从XML树取值后的实例对象
     * @throws Exception 异常
     */
    public static Object convertOutput(Object object) throws Exception {
        return convertOutputCore("", object);
    }

    /**
     * 将XML树转换成对象实例核心实现
     *
     * @param xmlPathPrefix XML树字段前缀
     * @param object        被实例化的对象类
     * @return 从XML树取值后的实例对象
     * @throws Exception 异常
     */
    private static Object convertOutputCore(String xmlPathPrefix, Object object) throws Exception {
        XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
        List<Field> fieldList = new ArrayList<>();
        Class<?> clasz = object.getClass();
        while (null != clasz) {
            fieldList.addAll(Arrays.asList(clasz.getDeclaredFields()));
            clasz = clasz.getSuperclass();
        }
        for (Field field : fieldList) {
            field.setAccessible(true);
            String fieldNamePath;

            String fieldTypeName = field.getType().getSimpleName();
            FIELDPATH fieldpath = field.getAnnotation(FIELDPATH.class);
            if (null != fieldpath) {
                //add by lizhangtao 如果有注解且为空字符串""则表示不希望从树中转换到对象
                if (StringUtil.isEmptyOrNull(fieldpath.value()) || NULL_STR.equalsIgnoreCase(fieldpath.value())) {
                    Type objectType = field.getGenericType();
                    if (null != objectType) {
                        //只有类才初始化，接口、枚举等都不初始化
                        if (!field.getClass().isInterface() && !field.getClass().isEnum()) {
                            if (objectType instanceof ParameterizedType) {
                                ParameterizedType parameterizedType = (ParameterizedType) objectType;
                                Class clazz = (Class) parameterizedType.getRawType();
                                if (!clazz.isInterface() && !clazz.isEnum()) {
                                    field.set(object, clazz.newInstance());
                                }
                            } else {
                                if (!isBaseDataType(field.getType())) {
                                    Class clazz = (Class) objectType;
                                    if (!clazz.isInterface() && !clazz.isEnum()) {
                                        field.set(object, clazz.newInstance());
                                    }
                                }
                            }
                        }
                    }
                    continue;
                }
                fieldNamePath = xmlPathPrefix.isEmpty() ? fieldpath.value() : (xmlPathPrefix + "/" + fieldpath.value());
            } else {
                fieldNamePath = xmlPathPrefix.isEmpty() ? field.getName() : (xmlPathPrefix + "/" + field.getName());
            }

            PropertyDescriptor propertyDescriptor;
            try {
                propertyDescriptor = new PropertyDescriptor(field.getName(), object.getClass());
            } catch (IntrospectionException e) {
                //add by lizhangtao 20210420 无此set方法说明不希望被赋值， 只是为了保证代码不抛出异常退出
                Type objectType = field.getGenericType();
                if (null != objectType) {
                    //只有类才初始化，接口、枚举等都不初始化
                    if (!field.getClass().isInterface() && !field.getClass().isEnum()) {
                        if (objectType instanceof ParameterizedType) {
                            ParameterizedType parameterizedType = (ParameterizedType) objectType;
                            Class clazz = (Class) parameterizedType.getRawType();
                            if (!clazz.isInterface() && !clazz.isEnum()) {
                                field.set(object, clazz.newInstance());
                            }
                        } else {
                            if (!isBaseDataType(field.getType())) {
                                Class clazz = (Class) objectType;
                                if (!clazz.isInterface() && !clazz.isEnum()) {
                                    field.set(object, clazz.newInstance());
                                }
                            }
                        }
                    }
                }
                continue;
            }
            Method setMechod = propertyDescriptor.getWriteMethod();
            String fieldValue = xmlTreeUtil.getXmlTreeStringValue(fieldNamePath.trim());
            switch (fieldTypeName) {
                case "int":
                case "Integer":
                    if (null != fieldValue && !fieldValue.isEmpty()) {
                        setMechod.invoke(object, Integer.parseInt(fieldValue));
                    }
                    break;
                case "long":
                case "Long":
                    if (null != fieldValue && !fieldValue.isEmpty()) {
                        setMechod.invoke(object, Long.parseLong(fieldValue));
                    }
                    break;
                case "short":
                case "Short":
                    if (null != fieldValue && !fieldValue.isEmpty()) {
                        setMechod.invoke(object, Short.parseShort(fieldValue));
                    }
                    break;
                case "double":
                case "Double":
                    if (null != fieldValue && !fieldValue.isEmpty()) {
                        setMechod.invoke(object, Double.parseDouble(fieldValue));
                    }
                    break;
                case "float":
                case "Float":
                    if (null != fieldValue && !fieldValue.isEmpty()) {
                        setMechod.invoke(object, Float.parseFloat(fieldValue));
                    }
                    break;
                case "String":
                case "BigDecimal":
                case "Object":
                    if (null != fieldValue && !fieldValue.isEmpty()) {
                        setMechod.invoke(object, fieldValue);
                    }
                    break;
                case "boolean":
                case "Boolean":
                    if (null != fieldValue && !fieldValue.isEmpty()) {
                        setMechod.invoke(object, "true".equals(fieldValue));
                    }
                    break;
                case "byte":
                case "byte[]":
                    if (null != fieldValue && !fieldValue.isEmpty()) {
                        setMechod.invoke(object, new Object[]{fieldValue.getBytes()});
                    }
                    break;
                case "char":
                case "Character":
                    if (null != fieldValue && !fieldValue.isEmpty()) {
                        setMechod.invoke(object, fieldValue.charAt(0));
                    }
                    break;
                case "Date":
                    if (null != fieldValue && !fieldValue.isEmpty()) {
                        setMechod.invoke(object, DateUtil.getDateStamp(fieldValue, "yyyy-MM-dd HH:mm:ss"));
                    }
                    break;
                case "List":
                    int k = 1;
                    String xmlListPathPrefix;
                    List<Object> objectList = new ArrayList<>();
                    if (null != fieldpath) {
                        xmlListPathPrefix = xmlPathPrefix.isEmpty() ? fieldpath.value() : (xmlPathPrefix + "/" + fieldpath.value());
                    } else {
                        xmlListPathPrefix = xmlPathPrefix.isEmpty() ? field.getName() : (xmlPathPrefix + "/" + field.getName());
                    }
                    Type type = field.getGenericType();
                    if (null != type) {
                        if (type instanceof ParameterizedType) {
                            ParameterizedType parameterizedType = (ParameterizedType) type;
                            Class clazz = (Class) parameterizedType.getActualTypeArguments()[0];
                            if (isBaseDataType(parameterizedType.getActualTypeArguments()[0])) {
                                String listValue = xmlTreeUtil.getXmlTreeStringValue((xmlListPathPrefix + "[" + k + "]"));
                                while (null != listValue) {
                                    objectList.add(listValue);
                                    listValue = xmlTreeUtil.getXmlTreeStringValue((xmlListPathPrefix + "[" + ++k + "]"));
                                    ;
                                }
                            } else {
                                while ((k == 1) || (k > 1 && objectList.size() > 0)) {
                                    Object objectValue = convertOutputCore((xmlListPathPrefix + "[" + k++ + "]"), clazz.newInstance());
                                    String xmlPath = xmlListPathPrefix + "[" + (k - 1) + "]";
                                    if (isNotEmpty(xmlPath, objectValue)) {
                                        objectList.add(objectValue);
                                    } else {
                                        break;
                                    }
                                }
                            }
                        }
                    }
                    if (!objectList.isEmpty()) {
                        setMechod.invoke(object, objectList);
                    }
                    break;
                default:
                    String xmlObjectPathPrefix;
                    if (null != fieldpath) {
                        xmlObjectPathPrefix = xmlPathPrefix.isEmpty() ? fieldpath.value() : (xmlPathPrefix + "/" + fieldpath.value());
                    } else {
                        xmlObjectPathPrefix = xmlPathPrefix.isEmpty() ? field.getName() : (xmlPathPrefix + "/" + field.getName());
                    }
                    Type ObjectType = field.getGenericType();
                    if (null != ObjectType) {
                        Class clazz = (Class) ObjectType;
                        Object objectValue = convertOutputCore(xmlObjectPathPrefix, clazz.newInstance());
                        setMechod.invoke(object, objectValue);
                    }
                    break;
            }
        }
        return object;
    }

    private static boolean isNotEmpty(String xmlPath, Object object) {
        XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
        if (null == object) {
            return false;
        } else {
            Field[] fields = object.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                try {
                    String fieldValue = xmlTreeUtil.getXmlTreeStringValue(xmlPath + "/" + field.getName());
                    if (null != fieldValue) {
                        return true;
                    }
                } catch (Exception e) {

                }
            }
        }
        return false;
    }


    /**
     * 判断对象是否为空
     *
     * @param object 对象
     * @return true-不为空,false-为空
     */
    @Deprecated
    private static boolean isNotEmpty(Object object) {
        if (null == object) {
            return false;
        } else {
            Field[] fields = object.getClass().getDeclaredFields();
            for (Field field : fields) {
                try {
                    field.setAccessible(true);
                    String fieldTypeName = field.getType().getSimpleName();
                    if ("int".equals(fieldTypeName) || "Integer".equals(fieldTypeName) || "long".equals(fieldTypeName) || "Long".equals(fieldTypeName) ||
                            "short".equals(fieldTypeName) || "Short".equals(fieldTypeName) || "byte".equals(fieldTypeName)) {
                        if (null != field.get(object)) {
                            String fieldNameValue = String.valueOf(field.get(object));
                            if (!"0".equals(fieldNameValue)) {
                                return true;
                            }
                        }
                    } else if ("double".equals(fieldTypeName) || "Double".equals(fieldTypeName) || "BigDecimal".equals(fieldTypeName) || "float".equals(fieldTypeName) || "Float".equals(fieldTypeName)) {
                        if (null != field.get(object)) {
                            String fieldNameValue = String.valueOf(field.get(object));
                            if (!"0.0".equals(fieldNameValue)) {
                                return true;
                            }
                        }
                    } else if ("Date".equals(fieldTypeName)) {
                        Date fieldNameValue = (Date) field.get(object);
                        if (null != fieldNameValue) {
                            return true;
                        }
                    } else if ("boolean".equals(fieldTypeName) || "Boolean".equals(fieldTypeName)) {
                        if (null != field.get(object)) {
                            Boolean fieldNameValue = (Boolean) field.get(object);
                            if (fieldNameValue) {
                                return true;
                            }
                        }
                    } else if ("String".equals(fieldTypeName) || "char".equals(fieldTypeName) || "Character".equals(fieldTypeName) || "byte[]".equals(fieldTypeName)) {
                        if (null != field.get(object)) {
                            String fieldNameValue = String.valueOf(field.get(object));
                            if (!StringUtil.isEmptyOrNull(fieldNameValue)) {
                                return true;
                            }
                        }
                    } else if ("List".equals(fieldTypeName)) {
                        List<Object> objectList = (List<Object>) object;
                        return isNotEmpty(objectList.get(0));
                    } else {
                        if (null != field.get(object)) {
                            return isNotEmpty(field.get(object));
                        }
                    }
                } catch (Exception e) {
                    return false;
                }
            }
        }
        return false;
    }

    /**
     * 判断是否为基本数据类型
     *
     * @param BaseDataType 基础数据类型
     * @return true-是，false-否
     */
    private static boolean isBaseDataType(Type BaseDataType) {
        return BaseDataType.equals(String.class) || BaseDataType.equals(Date.class) || BaseDataType.equals(DateTimeLiteralExpression.DateTime.class) ||
                BaseDataType.equals(Integer.class) || BaseDataType.equals(BigInteger.class) || "int".equals(BaseDataType.getTypeName()) ||
                BaseDataType.equals(Byte.class) || "byte".equals(BaseDataType.getTypeName()) || "byte[]".equals(BaseDataType.getTypeName()) ||
                BaseDataType.equals(BigDecimal.class) || BaseDataType.equals(Boolean.class) || "boolean".equals(BaseDataType.getTypeName()) ||
                BaseDataType.equals(Long.class) || "long".equals(BaseDataType.getTypeName()) ||
                BaseDataType.equals(Double.class) || "double".equals(BaseDataType.getTypeName()) ||
                BaseDataType.equals(Float.class) || "float".equals(BaseDataType.getTypeName()) ||
                BaseDataType.equals(Character.class) || "char".equals(BaseDataType.getTypeName()) ||
                BaseDataType.equals(Short.class) || "short".equals(BaseDataType.getTypeName());
    }
}
